{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Getting stuff into strings in Python\n",
    "\n",
    "By [Allison Parrish](http://www.decontextualize.com/)\n",
    "\n",
    "This is a little tutorial on how to interpolate values into strings in Python. As with many of my tutorials, we'll need a bit of randomness:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## String concatenation\n",
    "\n",
    "There are several ways to perform string interpolation in Python. The first is just to use the `+` operator to concatenate multiple strings together, like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "We have one hundred widgets in stock.\n"
     ]
    }
   ],
   "source": [
    "val = \"one hundred\"\n",
    "s = \"We have \" + val + \" widgets in stock.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This solution doesn't work so great, however, if the value that you want to interpolate isn't already a string type. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "must be str, not int",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-3-489b63743e03>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mval\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m100\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"We have \"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mval\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\" widgets in stock.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mTypeError\u001b[0m: must be str, not int"
     ]
    }
   ],
   "source": [
    "val = 100\n",
    "s = \"We have \" + val + \" widgets in stock.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You get a `TypeError`, which is means (essentially) \"hey, you told me to add two things together, but I don't know how to add a string to an integer; that doesn't even make sense.\" You can add strings to strings, and integers to integers, but not strings to integers. Those are just the rules.\n",
    "\n",
    "To get around this, you can use the `str()` function to get Python to make its best guess about how to represent the provided value as a string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'100'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str(val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "We have 100 widgets in stock.\n"
     ]
    }
   ],
   "source": [
    "s = \"We have \" + str(val) + \" widgets in stock.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This also works for floating point numbers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "fval = 3.14159265"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "We have 3.14159265 kilograms of widgets in stock.\n"
     ]
    }
   ],
   "source": [
    "s = \"We have \" + str(fval) + \" kilograms of widgets in stock.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A drawback of using string concatenation is that it gets messy quick when you start using more than one value.\n",
    "For example, imagine attempting to generate HTML using string concatenation. In the code below, I'm trying to build a `div` tag interpolating a random font, a random float, and a content string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<div style=\"font-family: 'Times New Roman'; float: right;\">hello!</div>\n"
     ]
    }
   ],
   "source": [
    "font = random.choice([\"'Comic Sans MS'\", \"'Times New Roman'\", \"Helvetica\"])\n",
    "float_val = random.choice([\"left\", \"right\"])\n",
    "content = \"hello!\"\n",
    "s = '<div style=\"font-family: ' + font + \"; float: \" + float_val + ';\">' + content + \"</div>\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The line with all of the concatenations is very difficult to read: on quick glance, it's not clear whether the quotes belong to the Python syntax or to the HTML syntax of the string I'm trying to create. The code is also brittle: you can imagine how difficult it would be to go in and make changes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `.format()` function\n",
    "\n",
    "One strategy for keeping code like this clean is finding ways to separate *form* and *content*. Ideally, we'd have a Python string that defines the structure of the output we want to create, and then a way to insert content into that structure. In other words, we want a *template* that can be \"filled in\" with arbitrary values. Python has a few ways of doing this. The most handy is the `.format()` function, which is available on all values of type `str`.\n",
    "\n",
    "Here's a simple use of the `.format()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "We have 10 widgets.\n"
     ]
    }
   ],
   "source": [
    "s = \"We have {count} widgets.\"\n",
    "output = s.format(count=10)\n",
    "print(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how it works. In this example, `s` is the \"template.\" The curly brackets in `s` are a special syntax that the `.format()` method understands, meaning \"leave this spot open to fill in later with something called `count`.\" When you call the `.format()` method, you supply [keyword arguments](https://docs.python.org/3/tutorial/controlflow.html#keyword-arguments) that correspond with the named placeholders in the template string. The method then replaces the placeholder with the value you supplied.\n",
    "\n",
    "You can have as many placeholder values as you'd like, and you can pick the names of your placeholders (there's nothing magical about the word `count`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"On Feb 17th, we'll have 12345 widgets in stock.\""
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"On {date}, we'll have {count} widgets in stock.\"\n",
    "s.format(date=\"Feb 17th\", count=12345)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "... but if there are placeholders in the string that don't receive a value in the parameters you pass to `.format()`, you'll get an error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'date'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-11-f2a2c1ff41d2>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"On {date}, we'll have {count} widgets in stock.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcount\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m: 'date'"
     ]
    }
   ],
   "source": [
    "s = \"On {date}, we'll have {count} widgets in stock.\"\n",
    "s.format(count=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although there is no corresponding error if you supply parameters that "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"On Feb 17th, we'll have 12345 widgets in stock.\""
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"On {date}, we'll have {count} widgets in stock.\"\n",
    "s.format(date=\"Feb 17th\", count=12345, tastiness=17)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Returning to our simple example of generating HTML, you can see that the solution with `.format()` is a little bit cleaner:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<div style=\"font-family: 'Times New Roman'; float: right;\">hello!</div>\n"
     ]
    }
   ],
   "source": [
    "font = random.choice([\"'Comic Sans MS'\", \"'Times New Roman'\", \"Helvetica\"])\n",
    "float_val = random.choice([\"left\", \"right\"])\n",
    "content = \"hello!\"\n",
    "s = '<div style=\"font-family: {font}; float: {float_val};\">{content}</div>'\n",
    "print(s.format(font=font, float_val=float_val, content=content))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this code, the `s` string now functions as a template that I can plug values into using `.format()`. I've separated the format from the content a little bit. The code is a bit clearer now and easier to edit.\n",
    "\n",
    "A common pattern you'll see in Python code is grouping the variables to be provided to the template in a dictionary, and then supplying that dictionary to the `.format()` method. Doing so requires the use of the unpacking operator (**):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<div style=\"font-family: 'Times New Roman'; float: left;\">hello!</div>\n"
     ]
    }
   ],
   "source": [
    "vals = {\n",
    "    'font': random.choice([\"'Comic Sans MS'\", \"'Times New Roman'\", \"Helvetica\"]),\n",
    "    'float_val': random.choice([\"left\", \"right\"]),\n",
    "    'content': \"hello!\"\n",
    "}\n",
    "s = '<div style=\"font-family: {font}; float: {float_val};\">{content}</div>'\n",
    "print(s.format(**vals))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Read more about the unpacking operator](https://realpython.com/python-kwargs-and-args/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To include a literal `{` or `}` in a formatted string, write `{{` or `}}`, respectively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'What?! This string has an actual { and an actual }.'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "word = random.choice([\"Wow\", \"Hey\", \"Whoa\", \"What?\", \"Nice\"])\n",
    "\"{interjection}! This string has an actual {{ and an actual }}.\".format(interjection=word)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `.format()` with positional parameters\n",
    "\n",
    "I wanted to mention a use of `.format()` that you're likely to see when looking examples up on the Internet. If you pass regular (non-keyword) comma-separated parameters to `.format()`, the placeholders in the template string are identified by index, not by name. Here's an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"On Feb 17th, we'll have 12345 widgets in stock.\""
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"On {0}, we'll have {1} widgets in stock.\"\n",
    "s.format(\"Feb 17th\", 12345)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use the same placeholder more than once, as this example that I like in [the official documentation](https://docs.python.org/3/library/string.html#formatspec) demonstrates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'abracadabra'"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'{0}{1}{0}'.format('abra', 'cad')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the `.format()` function has only one argument, you can use an empty pair of curly brackets in the template string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'hello there friend, how are you?'"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'hello there {}, how are you?'.format(\"friend\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Format specifications\n",
    "\n",
    "But there's more to turning data into strings than just including the most obvious string representation of a value in a template. The `.format()` method offers a few more tricks for getting your string output just right, most importantly *format specifiers*. A format specifier is a bit of code that you include inside template placeholders in order to modify the formatting of the placeholder's value in a more fine-grained fashion.\n",
    "\n",
    "There's a lot more to format specifications than we can cover here, so check [the official documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for more information and examples. Here are the basics.\n",
    "\n",
    "To use format specifications, follow the name in the placeholder (between the curly brackets) with a colon. After the colon, you can put a series of characters that correspond to the following categories of functionality.\n",
    "\n",
    "#### Alignment\n",
    "\n",
    "You can pad and/or align a parameter with the `alignment` operators `<` (left), `^` (center) and `>` (right), followed by a number specifying the width (in characters) to pad the string with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Good morning, Allison                       '"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"Good morning, {name:<30}\"\n",
    "s.format(name=\"Allison\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Good morning,                        Allison'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"Good morning, {name:>30}\"\n",
    "s.format(name=\"Allison\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Good morning,            Allison            '"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"Good morning, {name:^30}\"\n",
    "s.format(name=\"Allison\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you put a character in front of `<`, `^`, or `>`, the `.format()` method will use this to pad the string instead of an empty space:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Good morning, 🌞🌞🌞🌞🌞🌞🌞🌞🌞🌞🌞Allison🌞🌞🌞🌞🌞🌞🌞🌞🌞🌞🌞🌞'"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = \"Good morning, {name:🌞^30}\"\n",
    "s.format(name=\"Allison\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This works with integers as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Your serial number is 0000001745.'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"Your serial number is {count:0>10}.\".format(count=1745)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aaaaand it works with floating-point numbers, but there's a bit of weirdness:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'We have     172.45 kgs of widget in stock today.'"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"We have {amount:>10} kgs of widget in stock today.\".format(amount=172.45)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But consider:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'We have 3.142857142857143 kgs of widget in stock today.'"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"We have {amount:>10} kgs of widget in stock today.\".format(amount=22/7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case, the repeating decimal gets us in trouble, making `.format()` exceed the specified width. We'll discuss workarounds for this below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Integer bases\n",
    "\n",
    "The `b` and `x` presentation characters cause `.format()` to render the specified numbers as binary or hexadecimal numbers, respectively. For example, to render a number in binary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'10001'"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"{val:b}\".format(val=17)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can combine this with `<` to ensure that you're printing out an entire octet (i.e., byte):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'00010001'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"{val:0>8b}\".format(val=17)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Displaying an integer as a hexidecimal number:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2a'"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"{val:x}\".format(val=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is helpful for producing HTML-style hexadecimal color triplets:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'#19f0be'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"#{r:x}{g:x}{b:x}\".format(r=25, g=240, b=190)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Floating-point precision\n",
    "\n",
    "One of the main reasons to use format specifications is to format floating point numbers with a particular precision. The problem becomes evident when you're working with a number with a repeating decimal, or any other number with a large fractional component:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.142857142857143\n"
     ]
    }
   ],
   "source": [
    "pi_approx = 22 / 7\n",
    "print(pi_approx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "12.571428571428571\n"
     ]
    }
   ],
   "source": [
    "two_revolutions = 4 * pi_approx\n",
    "print(two_revolutions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Printing numbers like these to a particular precision (i.e., with a limited number of places after the decimal point) would be tricky using only string operations: you'd have to take into account the position of the decimal point, rounding, etc. An easier way is to use the `.` character and a following number in a format specification, with a trailing `f`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'how much pi? this much: 3.1429'"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"how much pi? this much: {val:.4f}\".format(val=pi_approx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'how much pi? this much: 12.5714'"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"how much pi? this much: {val:.4f}\".format(val=two_revolutions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A digit preceding the the `.` tells `.format()` to left-pad the number with spaces so the entire representation takes up that amount of space: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'how much pi? this much:   3.1429'"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"how much pi? this much: {val:8.4f}\".format(val=pi_approx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'how much pi? this much:  12.5714'"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"how much pi? this much: {val:8.4f}\".format(val=two_revolutions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use this to print out pretty tables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 115.73000\n",
      " 105.02891\n",
      "  87.79166\n",
      "  20.45000\n",
      " 127.02266\n",
      " 189.96105\n",
      " 172.28016\n",
      "  41.04942\n",
      " 104.92696\n",
      " 159.49087\n",
      "  94.17991\n",
      "  14.64796\n"
     ]
    }
   ],
   "source": [
    "vals = [random.random() * 200 for i in range(12)]\n",
    "for item in vals:\n",
    "    print(\"{val:10.5f}\".format(val=item))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## F-strings\n",
    "\n",
    "Since version 3.6, Python has supported [formatted string literals](https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings), commonly called *f-strings*. These work a lot like the `.format()` function, except the placeholder names in the template string are automatically filled in with the value of the corresponding variable. This is super handy and can save you quite a bit of typing!\n",
    "\n",
    "An f-string looks just like a regular string, except it has an `f` right before the initial quote. If there aren't any placeholders in an f-string, it behaves just like a regular string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mother said there'd be days like these.\n"
     ]
    }
   ],
   "source": [
    "s = f\"Mother said there'd be days like these.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But if you include placeholders, an f-string interpolates values into those placeholders from variables with the same name:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "On Feb 17th, we'll have 12345 widgets in stock.\n"
     ]
    }
   ],
   "source": [
    "date = \"Feb 17th\"\n",
    "count = 12345\n",
    "s = f\"On {date}, we'll have {count} widgets in stock.\"\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This makes our original HTML-replacement example much cleaner:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<div style=\"font-family: 'Comic Sans MS'; float: right;\">hello!</div>\n"
     ]
    }
   ],
   "source": [
    "font = random.choice([\"'Comic Sans MS'\", \"'Times New Roman'\", \"Helvetica\"])\n",
    "float_val = random.choice([\"left\", \"right\"])\n",
    "content = \"hello!\"\n",
    "s = f'<div style=\"font-family: {font}; float: {float_val};\">{content}</div>'\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the placeholder references a variable that doesn't exist, you get an error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'skateboard' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-42-7c94b54693d3>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'check out this rad {skateboard}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m: name 'skateboard' is not defined"
     ]
    }
   ],
   "source": [
    "print(f'check out this rad {skateboard}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The placeholders in f-strings support the same format specifications as those supported by `.format()`. For example, you can easily format floating-point numbers as strings like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.1429\n",
      "12.5714\n"
     ]
    }
   ],
   "source": [
    "print(f'{pi_approx:0.4f}')\n",
    "print(f'{two_revolutions:0.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A handy feature of f-strings is that (unlike `.format()` string interpolation) you can put arbitrary Python expressions inside of the curly brackets. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "544\n"
     ]
    }
   ],
   "source": [
    "print(f'{17 * 32}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HELLO\n"
     ]
    }
   ],
   "source": [
    "message = \"hello\"\n",
    "print(f'{message.upper()}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or even:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "THIS IS A TEST\n"
     ]
    }
   ],
   "source": [
    "words = [\"this\", \"is\", \"a\", \"test\"]\n",
    "print(f'{\" \".join([w.upper() for w in words])}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: Creating a chapbook of Mad Libs\n",
    "\n",
    "A [Mad Lib](https://en.wikipedia.org/wiki/Mad_Libs) is a \"phrasal template word game\" in which players fill in a template with missing words. Each of the blanks in the template has a label (like \"noun,\" \"verb,\" \"adjective,\" \"place name,\" etc.). Only one player can see the template, and that player prompts the others for words to fill in the template, giving them only the label as a prompt. It's great fun and also one of the basic forms of computational composition. ([House of Dust](http://zachwhalen.net/pg/dust/) by Alison Knowles and James Tenney is essentially a Mad Lib where the computer fills in the blanks at random.)\n",
    "\n",
    "In this example, I'm going to create a \"chapbook\" of short stories by filling in a template with randomly selected words. The template is going to generate HTML that you can open up in a browser.\n",
    "\n",
    "I'm starting with a template that just has boilerplate HTML, along with a few CSS styles (note the use of doubled curly brackets so that we end up with actual curly brackets in the output):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "html_tmpl = \"\"\"\n",
    "<!DOCTYPE html>\n",
    "<html lang=\"en\">\n",
    "<head>\n",
    "    <meta charset=\"utf-8\">\n",
    "    <title>{title}: A Computer-generated Chapbook</title>\n",
    "    <style>\n",
    "    h2 {{\n",
    "        page-break-before: always;\n",
    "    }}\n",
    "    p {{\n",
    "        width: 14em;\n",
    "        font-size: 14pt;\n",
    "    }}\n",
    "    </style>\n",
    "</head>\n",
    "<body>\n",
    "<h1>{title}</h1>\n",
    "{content}\n",
    "</body>\n",
    "</html>\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This string is intended to be used with the `.format()` method. It has two placeholders: `title` and `content`. You can fill it in like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "html_src = html_tmpl.format(title=\"Testing\", content=\"This is a test\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following function makes it possible to preview the resulting HTML right in our notebook, which is nice!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display, HTML\n",
    "def show_html(src):\n",
    "    return display(HTML(src), metadata=dict(isolated=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's what the resulting file looks like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<!DOCTYPE html>\n",
       "<html lang=\"en\">\n",
       "<head>\n",
       "    <meta charset=\"utf-8\">\n",
       "    <title>Testing: A Computer-generated Chapbook</title>\n",
       "    <style>\n",
       "    h2 {\n",
       "        page-break-before: always;\n",
       "    }\n",
       "    p {\n",
       "        width: 14em;\n",
       "        font-size: 14pt;\n",
       "    }\n",
       "    </style>\n",
       "</head>\n",
       "<body>\n",
       "<h1>Testing</h1>\n",
       "This is a test\n",
       "</body>\n",
       "</html>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {
      "isolated": true
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_html(html_src)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The code in the following cell writes the HTML out to a file on your hard drive:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"test.html\", \"w\") as fh:\n",
    "    fh.write(html_src)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you're using macOS, the following command will open the resulting file in your web browser. (If your system doesn't have the `open` command, you can locate the file using in the same folder as this notebook on your computer. Try using your browser's \"Open File...\" menu option.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "!open test.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Looks good so far! But it's not especially interesting. The code in the following cell uses a `for` loop and f-strings to build up a list of small stories, drawing on words selected at random from a fixed vocabulary, then shows the HTML fragment thereby generated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<h2>1. The Intercession</h2>\n",
      "<p>The intercession hastened the terrier. Nearby, the aggravated terrier dragged.</p>\n",
      "<h2>2. The Intercession</h2>\n",
      "<p>The intercession swung the boasting. Later, the occurring boasting corroded.</p>\n",
      "<h2>3. The Hallway</h2>\n",
      "<p>The hallway swung the line-up. Later, the disheveled boasting dragged.</p>\n",
      "<h2>4. The Defection</h2>\n",
      "<p>The defection tautened the boasting. Later, the perplexed terrier dragged.</p>\n",
      "<h2>5. The Defection</h2>\n",
      "<p>The defection corroded the defection. Nearby, the occurring boasting hastened.</p>\n",
      "<h2>6. The Boasting</h2>\n",
      "<p>The boasting drilled the terrier. Later, the perplexed defection dragged.</p>\n",
      "<h2>7. The Outdoors</h2>\n",
      "<p>The outdoors drilled the boasting. Nevertheless, the long-held intercession drilled.</p>\n",
      "<h2>8. The Hallway</h2>\n",
      "<p>The hallway drilled the equator. Meanwhile, the aggravated line-up charred.</p>\n",
      "<h2>9. The Line-up</h2>\n",
      "<p>The line-up hastened the intercession. Later, the codified intercession deployed.</p>\n",
      "<h2>10. The Defection</h2>\n",
      "<p>The defection hastened the defection. Later, the professional equator charred.</p>\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# number of pages to generate\n",
    "n_pages = 10\n",
    "\n",
    "# vocabulary\n",
    "nouns = ['hallway', 'intercession', 'equator', 'boasting', 'outdoors', 'terrier', 'line-up', 'defection']\n",
    "adjectives = ['disheveled', 'long-held', 'aggravated', 'occurring', 'perplexed', 'professional', 'corporatist', 'codified']\n",
    "verbs = ['corroded', 'deployed', 'tautened', 'dragged', 'swung', 'hastened', 'drilled', 'charred']\n",
    "adverbs = ['nearby', 'meanwhile', 'also', 'nevertheless', 'later']\n",
    "content = \"\"\n",
    "for i in range(n_pages):\n",
    "    # pick words for this story\n",
    "    protagonist = random.choice(nouns)\n",
    "    antagonist = random.choice(nouns)\n",
    "    onlooker = random.choice(nouns)\n",
    "    adj = random.choice(adjectives)\n",
    "    adv = random.choice(adverbs)\n",
    "    verb1 = random.choice(verbs)\n",
    "    verb2 = random.choice(verbs)\n",
    "    # concatenate together a series of f-strings...\n",
    "    story = f\"<h2>{i+1}. The {protagonist.capitalize()}</h2>\\n\"\n",
    "    story += f\"<p>The {protagonist} {verb1} the {antagonist}. \"\n",
    "    story += f\"{adv.capitalize()}, the {adj} {onlooker} {verb2}.</p>\"\n",
    "    story += \"\\n\"\n",
    "    # and then add to the content\n",
    "    content += story\n",
    "print(content)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tricky parts of this code:\n",
    "\n",
    "* The `+=` operator appends the string on the right to the string in the variable to the left.\n",
    "* Why `{i+1}`? I wanted to number the stories, but I didn't want the first one to be story #0.\n",
    "* The `.capitalize()` method capitalizes the first letter of every word in a string. (In this case, the strings only have one word.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As with the first example, we'll interpolate this content into the template, selecting a random noun as the title:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "html_src = html_tmpl.format(title=random.choice(nouns).capitalize(), content=content)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's what it looks like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<!DOCTYPE html>\n",
       "<html lang=\"en\">\n",
       "<head>\n",
       "    <meta charset=\"utf-8\">\n",
       "    <title>Hallway: A Computer-generated Chapbook</title>\n",
       "    <style>\n",
       "    h2 {\n",
       "        page-break-before: always;\n",
       "    }\n",
       "    p {\n",
       "        width: 14em;\n",
       "        font-size: 14pt;\n",
       "    }\n",
       "    </style>\n",
       "</head>\n",
       "<body>\n",
       "<h1>Hallway</h1>\n",
       "<h2>1. The Intercession</h2>\n",
       "<p>The intercession hastened the terrier. Nearby, the aggravated terrier dragged.</p>\n",
       "<h2>2. The Intercession</h2>\n",
       "<p>The intercession swung the boasting. Later, the occurring boasting corroded.</p>\n",
       "<h2>3. The Hallway</h2>\n",
       "<p>The hallway swung the line-up. Later, the disheveled boasting dragged.</p>\n",
       "<h2>4. The Defection</h2>\n",
       "<p>The defection tautened the boasting. Later, the perplexed terrier dragged.</p>\n",
       "<h2>5. The Defection</h2>\n",
       "<p>The defection corroded the defection. Nearby, the occurring boasting hastened.</p>\n",
       "<h2>6. The Boasting</h2>\n",
       "<p>The boasting drilled the terrier. Later, the perplexed defection dragged.</p>\n",
       "<h2>7. The Outdoors</h2>\n",
       "<p>The outdoors drilled the boasting. Nevertheless, the long-held intercession drilled.</p>\n",
       "<h2>8. The Hallway</h2>\n",
       "<p>The hallway drilled the equator. Meanwhile, the aggravated line-up charred.</p>\n",
       "<h2>9. The Line-up</h2>\n",
       "<p>The line-up hastened the intercession. Later, the codified intercession deployed.</p>\n",
       "<h2>10. The Defection</h2>\n",
       "<p>The defection hastened the defection. Later, the professional equator charred.</p>\n",
       "\n",
       "</body>\n",
       "</html>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {
      "isolated": true
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_html(html_src)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This cell writes the file out to `chapbook.html` and the following cell uses `open` to open it in your browser:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"chapbook.html\", \"w\") as fh:\n",
    "    fh.write(html_src)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "!open chapbook.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try printing this (or at least looking at it through print preview)! Because of the `page-break-before: always;` style on the `h2` tag, each story will be printed on a separate page."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Further resources\n",
    "\n",
    "* [More on f-strings](https://cito.github.io/blog/f-strings/)\n",
    "* [Official Python tutorial on string formatting](https://docs.python.org/3.8/tutorial/inputoutput.html#fancier-output-formatting)\n",
    "* [Tracery](http://tracery.io/) is a more sophisticated way of constructing strings from rules and templates. I wrote a [Python port of Tracery](https://github.com/aparrish/pytracery) along with a tutorial on how to use Python and Tracery ([Tracery and Python](https://github.com/aparrish/rwet/blob/master/tracery-and-python.ipynb)) and a tutorial on advanced Tracery syntax ([Making a Propp-inspired story generator in Tracery](http://catn.decontextualize.com/public/notebooks/propp-inspired-tracery.html)).\n",
    "* Need a refresher on HTML? I suggest [MDN's HTML tutorial](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
